home *** CD-ROM | disk | FTP | other *** search
- // -------------------------------------------------------------------------------------
- // PortComm.m
- // serial port communication handler
- // Author: Steve Herrick, Martin D. Flynn, NeXT Computer, Inc.
- // -------------------------------------------------------------------------------------
- // THIS CODE FRAGMENT IS FOR DEMONSTRATION PURPOSES ONLY.
- // Permission is granted to freely redistribute this source code, and to use fragments
- // of this code in your own applications if you find them to be useful. This class,
- // along with the source code, come with no warranty of any kind, and the user assumes
- // all responsibility for its use.
- // -------------------------------------------------------------------------------------
-
- // -------------------------------------------------------------------------------------
- #import <stdio.h>
- #import <fcntl.h>
- #import <string.h>
- #import <libc.h>
- #import <ctype.h>
- #import <mach/cthreads.h>
- #import <sys/types.h>
- #import <sys/time.h>
- #import <sys/uio.h>
- #import <sys/ioctl.h>
- #import <sys/socket.h>
- #import <netinet/in.h>
- #import <netdb.h>
- #import <objc/hashtable.h>
- #import "PortComm.h"
-
- // -------------------------------------------------------------------------------------
- #define EOL(C) (((C) == '\n') || ((C) == '\r'))
- #define mutexAlloc mutex_alloc
- #define mutexFree(X) mutex_free(X)
- #define mutexLock(X) mutex_lock(X)
- #define mutexUnlock(X) mutex_unlock(X)
-
- // -------------------------------------------------------------------------------------
- @implementation PortComm
-
- // -------------------------------------------------------------------------------------
- // forward/external declarations
- extern int close(int fd);
-
- // -------------------------------------------------------------------------------------
- static BOOL debug = NO; // debug mode
- static int openError = noERROR; // global error number
-
- // -------------------------------------------------------------------------------------
- #define validPORT(N) (((N) >= 0) && ((N) <= 1))
- static char *portDeviceTable[] = { portA, portB, 0 };
-
- // -------------------------------------------------------------------------------------
- // new instance
-
- /* new serial port instance */
- + newPort:(int)portNum baud:(u_char)baudRate parity:(u_short)parity charMask:(u_char)mask
- {
-
- char *portDev;
- BOOL eolCheck = YES;
- int ioSet, fd = -1;
- struct sgttyb sg;
- struct ltchars lc;
-
- /* single pass loop */
- for (openError = noERROR;;) {
-
- /* open serial port device */
- portDev = (char *)[PortComm getPortDevice:portNum];
- if (!portDev) { openError = errorBADPORTNUM; break; }
- if (debug) printf("PortComm: openning serial port #%d (%s)\n", portNum, portDev);
- fd = open(portDev, O_RDWR);
- if (fd < 0) { openError = errorOPEN; break; }
-
- /* prevent any other unauthorized port accesses */
- if (ioctl(fd, TIOCEXCL, (void*)nil) < 0) { openError = errorTIOCEXCL; break; }
-
- /* force line discipline */
- ioSet = OTTYDISC;
- if (ioctl(fd, TIOCSETD, &ioSet) < 0) { openError = errorTIOCSETD; break; }
-
- /* set local word mode */
- ioSet = LNOHANG /* | LPASS8OUT | LPASS8 */;
- if (ioctl(fd, TIOCLSET, &ioSet) < 0) { openError = errorTIOCLSET; break; }
-
- /* force everything else we can think of */
- if (ioctl(fd, TIOCCBRK , (void*)nil) < 0) { openError = errorTIOCCBRK ; break; }
- if (ioctl(fd, TIOCSDTR , (void*)nil) < 0) { openError = errorTIOCSDTR ; break; }
- if (ioctl(fd, TIOCNOTTY, (void*)nil) < 0) { openError = errorTIOCNOTTY; break; }
-
- /* set special chars */
- memset(&lc, 0xFF, sizeof(lc));
- if (ioctl(fd, TIOCSLTC, &lc) < 0) { openError = errorTIOCSLTC; break; }
-
- /* get current port configuration */
- if (ioctl(fd, TIOCGETP, &sg) < 0) { openError = errorTIOCGETP; break; }
-
- /* turn off flags */
- sg.sg_flags &= ~(ODDP | EVENP | ECHO | CBREAK | RAW | TANDEM);
-
- /* check parity */
- eolCheck = (parity == parityRAW)? NO : YES;
- if (parity == parityRAW_eol) parity = parityRAW;
-
- /* update port configuration */
- memset(&sg, 0, sizeof(sg));
- sg.sg_ispeed = baudRate;
- sg.sg_ospeed = baudRate;
- sg.sg_erase = 0xFF;
- sg.sg_kill = 0xFF;
- sg.sg_flags |= parity & (ODDP | EVENP | ANYP | RAW); // set parity
-
- /* set new port configuration */
- if (ioctl(fd, TIOCSETP, &sg) < 0) { openError = errorTIOCSETP; break; }
-
- /* break from single pass loop */
- break;
-
- }
-
- /* check for error */
- if (openError != noERROR) {
- if (fd >= 0) close(fd);
- if (debug) printf("PortComm: Unable to open serial port #%d\n", portNum);
- return (id)nil;
- }
-
- /* create instance */
- self = [[self alloc] init];
- portFd = fd;
- portNumber = portNum;
- portBaud = baudRate;
- portParity = parity;
- checkEol = eolCheck;
- charMask = (mask)? mask : 0xFF;
- if (debug) printf("PortComm: Successfully opened serial port #%d\n", portNum);
-
- return self;
- }
-
- + newPort:(int)portNum baud:(u_char)baudRate parity:(u_short)parity
- {
- return [self newPort:portNum baud:baudRate parity:parity charMask:0];
- }
-
- /* new instance of object */
- - init
- {
- [super init];
- isListening = NO;
- rcdBuff = (char*)nil;
- dataHandler = (id)nil;
- portFd = -1;
- portNumber = -1;
- portBaud = B0;
- portParity = parityUNKNOWN;
- portError = noERROR;
- forkMutex = mutexAlloc();
- writeMutex = mutexAlloc();
- maxBytes = 1024;
- charMask = 0xFF;
- checkEol = YES;
- return self;
- }
-
- /* close/free port object */
- - free
- {
- if (isListening) {
- printf("WARNING: 'free' issued while serial port handler thread is active\n");
- printf(" Application may terminate abnormally...\n");
- }
- close(portFd);
- if (rcdBuff) free(rcdBuff);
- mutexFree(forkMutex);
- mutexFree(writeMutex);
- return [super free];
- }
-
- /* return port file descriptor */
- - (int)portFd
- {
- return portFd;
- }
-
- // -------------------------------------------------------------------------------------
- // convert baud rate to actual defined value
- + (u_char)baudRateConstant:(int)baudRate
- {
- if (baudRate == 0) return B0;
- if (baudRate == 50) return B50;
- if (baudRate == 75) return B75;
- if (baudRate == 110) return B110;
- if (baudRate == 134) return B134;
- if (baudRate == 150) return B150;
- if (baudRate == 200) return B200;
- if (baudRate == 300) return B300;
- if (baudRate == 600) return B600;
- if (baudRate == 1200) return B1200;
- if (baudRate == 1800) return B1800;
- if (baudRate == 2400) return B2400;
- if (baudRate == 4800) return B4800;
- if (baudRate == 9600) return B9600;
- if (baudRate == 19200) return EXTA;
- if (baudRate == 38400) return EXTB;
- return B0;
- }
-
- // -------------------------------------------------------------------------------------
- // return port device name for port number
- + (const char *)getPortDevice:(int)portNum
- {
- return (validPORT(portNum))? portDeviceTable[portNum]: (char*)nil;
- }
-
- // -------------------------------------------------------------------------------------
- // check portnumber validity
-
- /* validate port number */
- + (BOOL)isValidPort:(int)portNum
- {
- return (portNum >= 0) && (portNum <= 1);
- }
-
- /* return port number */
- - (int)portNumber
- {
- return portNumber;
- }
-
- // -------------------------------------------------------------------------------------
- // set run flags
-
- /* set data handler id */
- - setDataHandler:handler
- {
- dataHandler = handler;
- return self;
- }
-
- /* set maximum read buffer size */
- - setMaxReadSize:(int)size
- {
- maxBytes = size;
- return self;
- }
-
- /* set debug mode */
- - setDebugMode:(BOOL)debugMode
- {
- debug = debugMode;
- return self;
- }
-
- /* set character mask */
- - setCharMask:(u_char)mask
- {
- charMask = mask;
- return self;
- }
-
- // -------------------------------------------------------------------------------------
-
- /* return port open error */
- + (int)openError
- {
- return openError;
- }
-
- /* return port error */
- - (int)portError
- {
- return portError;
- }
-
- // -------------------------------------------------------------------------------------
- // write data to port
-
- - (int)writeToPort:(char *)buff
- {
- return [self writeToPort:(char *)buff length:strlen(buff)];
- }
-
- - (int)writeToPort:(char *)buff length:(int)buffLen
- {
- int writeLen;
- mutexLock(writeMutex);
- if (debug) printf("PortComm: writing '%s' (len=%d)\n", buff, buffLen);
- writeLen = write(portFd, buff, buffLen);
- mutexUnlock(writeMutex);
- if (debug && (writeLen != buffLen)) printf("PortComm: write error! (%d)\n", writeLen);
- return (writeLen == buffLen)? 0: -1;
- }
-
- // -------------------------------------------------------------------------------------
- // do timeout
- - (int)readTimeout:(float)timeout
- {
- int flag, charsReady;
- u_long uSec;
- struct timeval tm;
- fd_set portList;
-
- /* get number of character currently ready */
- flag = ioctl(portFd, FIONREAD, &charsReady);
- if (flag < 0) return flag; // if error
- if (charsReady) return charsReady; // if characters ready
-
- /* wait for character to be present */
- if (debug) printf("PortComm: waiting for characters to be ready (%.1f)\n", timeout);
- FD_ZERO(&portList);
- FD_SET(portFd, &portList);
- uSec = (u_long)(timeout * 1000000.0);
- tm.tv_sec = uSec / 1000000L;
- tm.tv_usec = uSec % 1000000L;
- flag = select(portFd + 1, &portList, 0, 0, &tm);
- if (debug && (flag < 0)) printf("PortComm: character wait error! (%d)\n", flag); else
- if (debug && (flag == 0)) printf("PortComm: character wait timeout!\n");
-
- /* return select results */
- return flag;
-
- }
-
- // -------------------------------------------------------------------------------------
- // read data from port and return it in buff (null terminated)
- - (int)read:(char *)buff maxLen:(int)maxLen timeout:(float)timeout
- {
- int i, cnt;
-
- /* read from port */
- for (i = cnt = 0; cnt < maxLen;) {
-
- /* timeout for characters ready */
- if (timeout && ([self readTimeout:timeout] <= 0)) return -1;
-
- /* read data from port */
- if (debug) printf("PortComm: waiting on read\n");
- cnt += read(portFd, &buff[cnt], 1); // maxLen - cnt - 1
- if (charMask != 0xFF) for (;i < cnt; i++) buff[i] &= charMask;
- if (checkEol && (cnt > 0) && EOL(buff[cnt - 1])) break;
-
- }
-
- /* remove trailing CR/LF (if not in raw mode) */
- if (checkEol) {
- for (;(cnt > 0) && EOL(buff[cnt - 1]); buff[--cnt] = 0);
- buff[cnt] = 0;
- }
- if (debug) printf("PortComm: read '%s'\n", buff);
-
- return cnt;
- }
-
- // -------------------------------------------------------------------------------------
- // read data from port and send it to 'record' method
-
- /* thread function */
- static void readLoop(id self)
- {
- [self readLoop:0.0];
- cthread_exit(0);
- }
-
- - readLoop:(float)timeout
- {
- int cnt;
-
- /* allocate record buffer */
- mutexLock(forkMutex);
- if (rcdBuff) free(rcdBuff);
- rcdBuff = (char*)malloc(maxBytes + 1);
- mutexUnlock(forkMutex);
-
- /* read loop */
- for (;;) {
- cnt = [self read:rcdBuff maxLen:maxBytes timeout:timeout];
- mutexLock(forkMutex);
- [self dataRecord:rcdBuff len:cnt fromPort:portNumber];
- mutexUnlock(forkMutex);
- }
-
- /* return (never executed) */
- isListening = NO;
- return self;
-
- }
-
- // -------------------------------------------------------------------------------------
- // process data record (overridden by subclass)
- - (BOOL)dataRecord:(char*)buff len:(int)len fromPort:(int)portNum
- {
- if (dataHandler &&
- [dataHandler respondsTo:@selector(dataRecord:len:fromPort:)] &&
- (dataHandler != self) ) {
- [dataHandler dataRecord:buff len:len fromPort:portNum];
- return YES;
- }
- return NO;
- }
-
- // -------------------------------------------------------------------------------------
- // start read loop thread (from main thread)
- - forkPortListener
- {
- mutexLock(forkMutex);
- if (!isListening) {
- portThread = cthread_fork((cthread_fn_t)readLoop, (any_t)self);
- cthread_detach(portThread);
- isListening = YES;
- }
- mutexUnlock(forkMutex);
- return self;
- }
-
- @end
-